前端 您所在的位置:网站首页 promise race方法 前端

前端

#前端| 来源: 网络整理| 查看: 265

Promise.all和Promise.race传入空数组

关于将 Promise.all和 Promise.race传入空数组的两段代码的输出结果说法正确的是:

Promise.all([]).then(res => { console.log('all', res); // 输出all [] }) Promise.race([]).then(res => { console.log('race', res); // 没有任何输出 })

Promise.all([])会返回一个成功状态的promise

Promise.race([])会返回一个pending状态的promise

promise的超时控制

Promise原生是不支持设置超时时间的,也不支持中断请求。如果我们想要实现超时控制或者取消重复请求这样的需求,只能寻求其它思路,另外这两个需求本质上都是中断请求,并且我们可以随

控制什么时候中断。

总体思路是:创建一个新的newPromise,和原来用于发送请求的originPromise作为参数传入Promise.race方法中,接下来使用这个race方法返回的promise,上边添加then和catch方法。如果

要中断原来的promise,只需要将newPromise变成失败状态即可。

需求1:自定义实现超时控制

请求是无法设置超时时间的,因此我们需要自己去模拟一个超时控制。

方法一:使用promise.race // 封装一个延时失败的promise function sleep(time) { return new Promise((resolve, reject) => { setTimeout(() => { reject('timeout') }, time); }) } // 第一个参数是正常的promise,第二个参数设置超时时间 function timeoutPromise(promise, time) { return Promise.race([promise, sleep(time)]); } // 测试 function createPromise(time) { return new Promise((resolve, reject) => { setTimeout(() => { resolve('done'); }, time); }) } // 超时。设置正常的promise在2s后成功,超时时间是1s timeoutPromise(createPromise(2000), 1000).then(res => { console.log(res); }).catch(err => { console.log(err); // timeout }); // 不超时 timeoutPromise(createPromise(2000), 3000).then(res => { console.log(res); // done }).catch(err => { console.log(err); }); 方法二:不使用promise.race。但是本质上相当于自定义实现了promise.race,手动创建一个promise的数组,让每一个promise都开始执行,谁的状态先改变就以谁的状态为准 function sleep(time) { return new Promise((resolve, reject) => { setTimeout(() => { reject('timeout') }, time); }); } function timeoutPromise(promise, time) { const promiseArr = [promise, sleep(time)]; return new Promise((resolve, reject) => { promiseArr.forEach(p => { p.then(res => { resolve(res); }).catch(err => { reject(err); }) }) }) } // 测试 function createPromise(time) { return new Promise((resolve, reject) => { setTimeout(() => { resolve('done'); }, time); }) } // 超时 timeoutPromise(createPromise(2000), 1000).then(res => { console.log(res); }).catch(err => { console.log(err); // timeout }); // 不超时 timeoutPromise(createPromise(2000), 3000).then(res => { console.log(res); // done }).catch(err => { console.log(err); }); 需求2:转盘问题

转盘问题,一个抽奖转盘动画效果有5秒,但是一般来说向后端请求转盘结果只需要不到一秒,因此请求结果至少得等5秒才能展现给用户。

需要考虑两种情况。

转盘动画还未完成,请求结果已经拿到了,此时要等到动画完成再展示结果给用户。 转盘动画完成了,请求结果还未拿到,此时需要等待结果返回(可以设置请求超时时间)。

所以,转盘问题更适合用Promise.all()来解决。

// 转盘动画在time秒后完成 function plate(time) { return new Promise(resolve => { setTimeout(() => { resolve('我转完了~'); }, time); }) } // 第一个参数是包裹着请求的promise,第二个参数是转盘动画时间 function wrapPromise(promise, time) { return Promise.all([promise, plate(time)]); } // 测试 function createPromise(time) { return new Promise((resolve, reject) => { setTimeout(() => { resolve('done'); }, time); }) } wrapPromise(createPromise(2000), 3000).then(res => { console.log(res); // ["done", "我转完了~"] }); wrapPromise(createPromise(2000), 1000).then(res => { console.log(res); //  ["done", "我转完了~"] }); 取消重复请求

同一类请求是有序发出的(根据按钮点击的次序),但是响应顺序却是无法预测的,我们通常只希望渲染最后一次发出请求响应的数据,而其他数据则丢弃。因此,我们需要丢弃(或不处理)除最

一次请求外的其他请求的响应数据。

function CancelablePromise() { this.pendingPromise = null; } // 参数是一个正常的包裹着请求的promise CancelablePromise.prototype.request = function (promise) { if (this.pendingPromise) { this.cancel('取消重复请求'); } const _promise = new Promise((resolve, reject) => { this.reject = reject; }); const newPromise = Promise.race([promise, _promise]); this.pendingPromise = newPromise; return newPromise; } CancelablePromise.prototype.cancel = function (reason) { this.reject(new Error(reason)); this.pendingPromise = null; } // 测试 function createRequest(delay) { return new Promise((resolve, reject) => { setTimeout(() => { resolve('请求完成') }, delay); }) } const cancelablePromise = new CancelablePromise(); for (let i = 0; i < 3; i++) { cancelablePromise.request(createRequest(1000)).then(res => { console.log(res); }).catch(err => { console.error(err); }) } 限制并发请求数

Promise.allSettled不适合应对这样的场景,它能控制的粒度还是太粗了。它必须等待所有Promise都resolve或reject后才能继续。比如现在总共有10个请求,并发请求数限制为4个,当其中

个请求完成时,应该就可以进行下一个请求了,为了最高效率,要始终保证并发请求数量被最大程度使用。但是如果使用Promise.allSettled,它必须等待4个请求都完成了才能进行接下来的4个

求,这显然是不合理的。

function limitRequest(requestFnArr, limit) { function request(requestFn) { requestFn().finally(() => { // 如果前边的请求进行完成了,就可以处理新的请求 if (_requestFnArr.length > 0) { request(_requestFnArr.shift()); } }) } // 限制并发量 const _requestFnArr = [...requestFnArr]; for (let i = 0; i < limit && _requestFnArr.length > 0; i++) { request(_requestFnArr.shift()); } } // 测试 function createRequest(delay) { return function () { return new Promise((resolve, reject) => { setTimeout(() => { resolve('done') }, delay); }).then(res => console.log(res); }) } } const requestFns = []; for (let i = 0; i < 10; i++) { requestFns.push(createRequest(1000)); } limitRequest(requestFns, 4); 实现有并行限制的Promise调度器 class Scheduler { constructor(limit) { this.limit = limit; this.count = 0; this.taskList = []; } run(promiseCreator) { promiseCreator().finally(() => { this.count--; if (this.taskList.length > 0) { this.run(this.taskList.shift()); } }) } add(promiseCreator) { this.taskList.push(promiseCreator); if (this.count < this.limit) { this.run(this.taskList.shift()); this.count++; } } } // 测试 const scheduler = new Scheduler(3); const timeout = time => new Promise(resolve => { setTimeout(resolve, time); }); const addTask = (time, value) => { scheduler.add(() => { return timeout(time).then(() => { console.log(value); }) }) }; addTask(1000, '1'); addTask(500, '2'); addTask(300, '3'); addTask(400, '4'); 自定义实现allSettled function allSettled(promises) { let result = []; let count = 0; return new Promise(resolve => { for (let i = 0; i < promises.length; i++) { let promise = Promise.resolve(promises[i]); let obj = {}; promise.then(res => { obj = { status: 'fulfilled', value: res } }).catch(err => { obj = { status: 'rejected', reason: err } }).finally(() => { // 注意这里需要根据下标来存值,否则无法保证返回值数组与参数顺序一致 result[i] = obj; if (++count === promises.length) { resolve(result); } }) } }) } 自定义实现Promise.all和Promise.race Promise.all: function promiseAll(promises) { // 传入的参数不一定是数组对象,可以是"遍历器" // 如果参数是一个真正的数组,Array.from会返回一个一模一样的新数组。 promises = Array.from(promises); let count = 0; let newValues = []; return new Promise(((resolve, reject) => { for (let i = 0; i < promises.length; i++) { Promise.resolve(promises[i]) .then(res => { // 保存这个promise实例的value // 注意这里需要根据下标来存值,否则无法保证返回值数组与参数顺序一致 result[i] = res; // 通过计数器,标记是否所有实例均 fulfilled if (++count === promises.length) { resolve(newValues); } }) .catch(err => { reject(err); }) } })) }function promiseAll(promises) { // 传入的参数不一定是数组对象,可以是"遍历器" // 如果参数是一个真正的数组,Array.from会返回一个一模一样的新数组。 promises = Array.from(promises); let count = 0; let newValues = []; return new Promise(((resolve, reject) => { for (let i = 0; i < promises.length; i++) { Promise.resolve(promises[i]) .then(res => { // 保存这个promise实例的value // 注意这里需要根据下标来存值,否则无法保证返回值数组与参数顺序一致 result[i] = res; // 通过计数器,标记是否所有实例均 fulfilled if (++count === promises.length) { resolve(newValues); } }) .catch(err => { reject(err); }) } })) } Promise.race: function race(promises) { return new Promise((resolve, reject) => { for (let i = 0; i < promises[i]; i++) { Promise.resolve(promises[i]).then(res => { resolve(res); }).catch(err => { reject(err); }) } }) }

问:上边代码中能否直接将resolve传递给then方法,或者能否直接将reject传递给catch方法?

是可以的。当原来的promise变为成功状态时,会去调用then的函数参数,并将原来调用resolve时传递的参数作为then的回调函数的参数。所以可以写的更简洁:

function race(promiseArr) { return new Promise((resolve, reject) => { promiseArr.forEach(p => { Promise.resolve(p) .then(resolve) .catch(reject); }); }); } 全局捕获promise异常

业务场景:团队开发中可能有同学忘记处理promise的异常,此时全局中应该对此异常进行捕获,做一个兜底。

new Promise((resolve, reject) => { throw new Error("出错了"); }).then((res) => { console.log(res); }); window.addEventListener('unhandledrejection', event => { const { error, // 错误对象 promise, // 出现异常的promise对象 } = event console.log(error, promise) }) 对比Promise.race方法,实现一个last方法

描述:race方式是接收一个数组,只要其中有一个promise实例的状态率先改变,最终promise的实例就会改变。现在要对比着自定义实现一个last方法,不管前边的promise状态怎么变,我们只

据最后一个发生状态改变的promise来决定最终promise实例的状态。

new Promise((resolve, reject) => { throw new Error("出错了"); }).then((res) => { console.log(res); }); window.addEventListener('unhandledrejection', event => { const { error, // 错误对象 promise, // 出现异常的promise对象 } = event console.log(error, promise) }) 实现并行发送请求的函数

要求实现一个send(list, n, callback)函数,该函数接收三个参数,第一个参数是存储url的数组,第一个参数是限制的并发请求数,第三个参数是回调函数。要求在发送请求的过程中收集数据

存入数组中,数据和请求发送的顺序要一一对应。最后所有请求完成时,调用callback([data1, data2, data3, ...])。本题假设所有请求都会被正常处理,有返回结果。

// 请求函数 function myFetch(url) { return new Promise(resolve => { const timeout = parseInt(Math.random() * 3 + 1); setTimeout(() => { resolve(url); }, timeout * 1000); }); } // 实现的send函数 function send(list, n, callback) { let num = 0; let count = 0; const data = []; const fn = (url, index) => { num++; myFetch(url).then(res => { data[index] = res; if (num < list.length) { fn(list[num], num); } if (++count === list.length) { callback(data); } }); }; for (let i = 0; i < n && i < list.length; i++) { fn(list[i], i); } } // 测试 const list = [1, 2, 3, 4, 5, 6, 7, 8, 9]; function callback(data) { console.log(data); } send(list, 5, callback); send(list, 15, callback);


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有